const PREC = {
  CALL: 12,
  UNA: 8,
  MUL: 7,
  ADD: 6,
  COMP: 5,
  ANDOR: 4,
};

module.exports = grammar({
  name: 'ming',

  extras: $ => [/\s/, $._comment],
  supertypes: $ => [
    $.stmt,
    $.expr,
    $.primary,
    $.decl,
  ],

  rules: {
    code: $ => rep_sepn($.stmt, ";"),
    stmt: $ => choice($.decl, $.fn, $.asn, $.type, $.expr),
    // decl
    decl: $ => choice($.const, $.let, $.mut, $.var),
    const: $ => seq('const', $.name, '=', $.expr),
    let: $ => seq('let', $.name, '=', $.expr),
    mut: $ => seq('mut', $.name, '=', $.expr),
    var: $ => seq('var', $.name, '=', $.expr),
    // fn
    fn: $ => seq('fn', $.name, optional($.params), $.body),
    params: $ => seq('(', optional(seq($.name, repeat(seq(',', $.name)))), ')'),
    body: $ => seq('{', rep_sepn($.stmt, ';'), '}'),
    // asn
    asn: $ => seq($.name, '=', $.expr),
    // type
    type: $ => seq('type', $.name, choice(
      seq('=', $.expr),
      seq('{', rep_sepn($.field, ';'), '}'),
    )),
    field: $ => seq($.name, /[\s]+/, $.expr),
    // expr
    expr: $ => choice($.primary, $.una, $.bina),
    primary: $ => choice(alias($.group, 'group'), $.call, $.name, $.int, $.float, $.str, $.bool),
    una: $ => prec.left(PREC.UNA, seq(choice('-', '!'), $.expr)),
    bina: $ => choice(
      prec.left(PREC.MUL, seq($.expr, '*', $.expr)),
      prec.left(PREC.MUL, seq($.expr, '/', $.expr)),
      prec.left(PREC.ADD, seq($.expr, '+', $.expr)),
      prec.left(PREC.ADD, seq($.expr, '-', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '==', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '!=', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '>=', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '<=', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '>', $.expr)),
      prec.left(PREC.COMP, seq($.expr, '<', $.expr)),
      prec.left(PREC.ANDOR, seq($.expr, 'and', $.expr)),
      prec.left(PREC.ANDOR, seq($.expr, 'or', $.expr)),
    ),
    group: $ => seq('(', $.expr, ')'),
    call: $ => prec.left(PREC.CALL, seq($.name, $.args)),
    args: $ => seq('(', rep_sepn(choice($.expr, $.pair), ','), ')'),
    pair: $ => seq(alias($.name, $.key), ':', $.expr),
    // values
    name: (_) => {
      const name_start =
        /[^\p{Control}\s+\-*/%^#&~|<>=(){}\[\];:,.\\'"\d!]/;
      const name_continue =
        /[^\p{Control}\s+\-*/%^#&~|<>=(){}\[\];:,.\\'"!]*/;
      return token(seq(name_start, name_continue));
    },
    int: $ => /\d+/,
    float: $ => /\d+\.\d+/,
    str: $ => /".*?"/,
    bool: $=> choice(token('true'), token('false')),
    _comment: (_) =>
      token(choice(seq("//", /[^\n]*/), seq("/*", /[^*]*\*+([^/*][^*]*\*+)*/, "/"))),
  }
});

function rep_sepn(rule, sep) {
  return optional(seq(rule, repeat(seq(choice(sep, '\n'), rule)), optional(choice(sep, '\n'))));
}